/////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
/////////////////////////////////////////////////////////////////////////////


#include "EASTLTest.h"
#include <EASTL/array.h>
#include <EABase/eabase.h>



using namespace eastl;



// Template instantations.
// These tell the compiler to compile all the functions for the given class.
template struct eastl::array<int, 0>;
template struct eastl::array<int, 5>;
template struct eastl::array<Align32, 5>; // VC++ fails to compile due to error generated by the swap function. C2718: http://msdn.microsoft.com/en-us/library/vstudio/sxe76d9e.aspx

template<typename T> class TP;

// test whether typename tuple_element<I, T>::type is defined:
template<size_t I, typename T, typename = void>
struct has_tuple_element_type : false_type {};

template<size_t I, typename T>
struct has_tuple_element_type<I, T, void_t<typename tuple_element<I, T>::type>> : true_type {};

static_assert(eastl::is_aggregate<eastl::array<int, 0>>::value, "must be an aggregate");
static_assert(eastl::is_aggregate<eastl::array<int, 5>>::value, "must be an aggregate");

int TestArray()
{
	int nErrorCount = 0;

	{
		array<int, 5> a = { { 0, 1, 2, 3, 4 } };
		array<int, 5> b = { { 0, 1, 2, 3    } };
		array<int, 5> c = { { 4, 3, 2, 1, 0 } };
		array<int, 0> d = {};

		VERIFY(!a.empty());
		VERIFY(a.size() == 5);
		VERIFY(a[0] == 0);
		VERIFY(a[4] == 4);

		VERIFY(!b.empty());
		VERIFY(b.size() == 5);
		VERIFY(b[0] == 0);
		VERIFY(b[3] == 3);

		VERIFY(d.empty());
		VERIFY(d.size() == 0);

		// swap
		a.swap(c);
		VERIFY(a[0] == 4);
		VERIFY(c[0] == 0);

		// begin, end
		array<int, 5>::iterator it = a.begin();
		VERIFY((a.validate_iterator(it) & (isf_valid | isf_can_dereference)) != 0);
		VERIFY(*it == 4);

		++it;
		VERIFY(*it == 3);

		++it;
		VERIFY(*it == 2);

		--it;
		VERIFY(*it == 3);

		it += 3;
		VERIFY((a.validate_iterator(it) & (isf_valid | isf_can_dereference)) != 0);
		VERIFY(*it == 0);

		++it;
		VERIFY(it == a.end());
		VERIFY((a.validate_iterator(it) & isf_valid) != 0);
		VERIFY(a.validate());

		// rbegin, rend
		array<int, 5>::reverse_iterator itr = a.rbegin();
		VERIFY((a.validate_iterator(itr.base()) & (isf_valid | isf_can_dereference)) != 0);
		VERIFY(*itr == 0);

		itr++;
		VERIFY(*itr == 1);

		// data
		int* pArray = a.data();
		VERIFY(pArray == a.mValue);

		// front
		int& nFront = a.front();
		VERIFY(nFront == 4);

		// back
		int& nBack = a.back();
		VERIFY(nBack == 0);

		// at
		VERIFY(a[0] == a.at(0));
		#if EASTL_EXCEPTIONS_ENABLED
			bool bExceptionOccurred = false;
			try{
				int x = a.at(100);
				VERIFY(x != -1);
			}
			catch(...){
				bExceptionOccurred = true;
			}
			VERIFY(bExceptionOccurred);
		#endif

		// global operators
		a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3; a[4] = 4;  // 01234
		b[0] = 0; b[1] = 1; b[2] = 2; b[3] = 3; b[4] = 4;  // 01234
		c[0] = 0; c[1] = 1; c[2] = 2; c[3] = 3; c[4] = 9;  // 01239

		VERIFY( (a == b));
		VERIFY(!(a != b));
		VERIFY(!(a  < b));
		VERIFY( (a <= b));
		VERIFY( (a >= b));
		VERIFY(!(a  > b));

		VERIFY(!(a == c));
		VERIFY( (a != c));
		VERIFY( (a  < c));
		VERIFY( (a <= c));
		VERIFY(!(a >= c));
		VERIFY(!(a  > c));

#if defined(EA_COMPILER_HAS_THREE_WAY_COMPARISON)
		VERIFY( (a <=> b) == 0);
		VERIFY(!((a <=> b) != 0));
		VERIFY(!((a <=> b) < 0));
		VERIFY( (a <=> b) <= 0);
		VERIFY( (a <=> b) >= 0);
		VERIFY(!((a <=> b) > 0));

		VERIFY(!((a <=> c) == 0));
		VERIFY( (a <=> c) != 0);
		VERIFY( (a <=> c) < 0);
		VERIFY( (a <=> c) <= 0);
		VERIFY(!((a <=> c) >= 0));
		VERIFY(!((a <=> c) > 0));
#endif

		// deduction guides
		#ifdef __cpp_deduction_guides
			array deduced {1,2,3,4,5};

			static_assert(eastl::is_same_v<decltype(deduced)::value_type, int>, "deduced array value_type mismatch");
			VERIFY(deduced.size() == 5);
		#endif

		eastl::array<int, 0> empty_array;
		VERIFY(empty_array.size() == 0); // array with 0 elements is empty

		// structured binding

		{
			eastl::array<int, 5> aCopy = a;
			auto&& [a0, a1, a2, a3, a4] = aCopy;

			VERIFY(a1 == aCopy[1]);
			VERIFY(a2 == aCopy[2]);
			VERIFY(a3 == aCopy[3]);
			VERIFY(a4 == aCopy[4]);

			a0 = 100;
			VERIFY(aCopy[0] == 100);

			a4 = 0;
			VERIFY(aCopy[4] == 0);

			// The deduced type may or may not be a reference type; it is an aliased type,
			//  as per https://en.cppreference.com/w/cpp/language/structured_binding:
			//  > Like a reference, a structured binding is an alias to an existing object. Unlike a reference,
			//    the type of a structured binding does not have to be a reference type.
			// Any reference specifier is thus removed to check only the type & its const qualifier
			static_assert(eastl::is_same_v<eastl::remove_reference_t<decltype(a0)>, int>);

			const eastl::array<int, 5> aConstCopy = a;
			auto&& [aConst0, aConst1, aConst2, aConst3, aConst4] = aConstCopy;

			static_assert(eastl::is_same_v<eastl::remove_reference_t<decltype(aConst0)>, const int>);
		}
	}

	// constexpr tests
	{
	#ifndef EA_NO_CPP14_CONSTEXPR
		EA_CPP14_CONSTEXPR eastl::array<int, 4> a = {{ 0, 1, 2, 3 }};

		static_assert(a == eastl::array<int, 4>{{ 0, 1, 2, 3 }}, "array constexpr failure");

		static_assert(a[0] == 0, "array constexpr failure");
		static_assert(a[1] == 1, "array constexpr failure");
		static_assert(a[2] == 2, "array constexpr failure");
		static_assert(a[3] == 3, "array constexpr failure");

		static_assert(a.at(0) == 0, "array constexpr failure");
		static_assert(a.at(1) == 1, "array constexpr failure");
		static_assert(a.at(2) == 2, "array constexpr failure");
		static_assert(a.at(3) == 3, "array constexpr failure");

		static_assert(a.data()[0] == 0, "array constexpr failure");
		static_assert(a.data()[1] == 1, "array constexpr failure");
		static_assert(a.data()[2] == 2, "array constexpr failure");
		static_assert(a.data()[3] == 3, "array constexpr failure");

		static_assert(a.empty() == false, "array constexpr failure");
		static_assert(a.size() == 4, "array constexpr failure");
		static_assert(a.max_size() == 4, "array constexpr failure");

		static_assert(a.front() == 0, "array constexpr failure");
		static_assert(a.back() == 3, "array constexpr failure");

		static_assert(a.begin()[0] == 0, "array constexpr failure");
		static_assert(a.begin()[1] == 1, "array constexpr failure");
		static_assert(a.begin()[2] == 2, "array constexpr failure");
		static_assert(a.begin()[3] == 3, "array constexpr failure");

		static_assert(a.cbegin()[0] == 0, "array constexpr failure");
		static_assert(a.cbegin()[1] == 1, "array constexpr failure");
		static_assert(a.cbegin()[2] == 2, "array constexpr failure");
		static_assert(a.cbegin()[3] == 3, "array constexpr failure");

		static_assert(a.crbegin()[0] == 3, "array constexpr failure");
		static_assert(a.crbegin()[1] == 2, "array constexpr failure");
		static_assert(a.crbegin()[2] == 1, "array constexpr failure");
		static_assert(a.crbegin()[3] == 0, "array constexpr failure");

		static_assert(a.end()[-1] == 3, "array constexpr failure");
		static_assert(a.end()[-2] == 2, "array constexpr failure");
		static_assert(a.end()[-3] == 1, "array constexpr failure");
		static_assert(a.end()[-4] == 0, "array constexpr failure");

		static_assert(a.cend()[-1] == 3, "array constexpr failure");
		static_assert(a.cend()[-2] == 2, "array constexpr failure");
		static_assert(a.cend()[-3] == 1, "array constexpr failure");
		static_assert(a.cend()[-4] == 0, "array constexpr failure");

		static_assert(a.crend()[-1] == 0, "array constexpr failure");
		static_assert(a.crend()[-2] == 1, "array constexpr failure");
		static_assert(a.crend()[-3] == 2, "array constexpr failure");
		static_assert(a.crend()[-4] == 3, "array constexpr failure");
	#endif
	}

	// tuple_size / tuple_element
	{
		static_assert(tuple_size_v<array<int, 0>> == 0, "tuple_size");
		static_assert(tuple_size_v<array<int, 1>> == 1, "tuple_size");
		static_assert(tuple_size_v<array<int, 2>> == 2, "tuple_size");
		static_assert(tuple_size_v<array<int, 3>> == 3, "tuple_size");

		static_assert(is_same_v<tuple_element_t<2, array<int, 3>>, int>, "tuple_element_t");
		static_assert(is_same_v<tuple_element_t<0, array<int, 5>>, int>, "tuple_element_t");
		static_assert(is_same_v<tuple_element_t<3, array<int, 5>>, int>, "tuple_element_t");

		static_assert(has_tuple_element_type<3, array<int, 5>>::value, "tuple_element_t");

		// tuple_element_t with the following parameters is a compile error:
		static_assert(!has_tuple_element_type<0, array<int, 0>>::value, "tuple_element_t");
		static_assert(!has_tuple_element_type<1, array<int, 1>>::value, "tuple_element_t");
		static_assert(!has_tuple_element_type<10, array<int, 8>>::value, "tuple_element_t");
	}

	// get<I>()
	{
		{
			eastl::array<int, 2> arr{ 1, 3 };
			get<0>(arr) = 4;
			VERIFY(arr[0] == 4);
		}

		{
			const eastl::array<int, 3> arr{ 5, 6, -10 };
			VERIFY(get<2>(arr) == -10);
			VERIFY(get<0>(eastl::array<int, 2>{7, 8}) == 7);
			VERIFY(get<1>(eastl::array<int, 2>{7, 8}) == 8);
		}
	}

#ifndef EA_COMPILER_NO_STRUCTURED_BINDING
	// C++17 structured binding
	{
		{
			eastl::array<int, 2> arr{ 1, 3 };
			auto [a, b] = arr; // get(array<T,N>&&)
			VERIFY(a == 1);
			VERIFY(b == 3);
			auto& [c, d] = arr; // get(array<T,N>&)
			VERIFY(c == 1);
			VERIFY(d == 3);
			c = 5;
			d = 6;
			VERIFY(arr[0] == 5);
			VERIFY(arr[1] == 6);
		}

		{
			eastl::array<int, 5> arr{ 0, 2, 4, 6, 8 };
			const auto [a, b, c, d, e] = arr; // get(const array<T,N>&&)
			VERIFY(a == 0);
			VERIFY(b == 2);
			VERIFY(c == 4);
			VERIFY(d == 6);
			VERIFY(e == 8);
			const auto& [f,g,h,i,j] = arr; // get(const array<T,N>&)
			VERIFY(f == 0);
			VERIFY(g == 2);
			VERIFY(h == 4);
			VERIFY(i == 6);
			VERIFY(j == 8);
			arr[0] = 5;
			arr[1] = 6;
			VERIFY(f == 5);
			VERIFY(g == 6);
		}
	}
#endif

	// to_array
	{
		{
			constexpr int c_array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
			constexpr auto arr = to_array(c_array);

			static_assert(is_same_v<remove_cv_t<decltype(arr)>, eastl::array<int, 10>>, "unexpected return type");

			static_assert(arr[0] == 0, "unexpected array value");
			static_assert(arr[1] == 1, "unexpected array value");
			static_assert(arr[2] == 2, "unexpected array value");
			static_assert(arr[3] == 3, "unexpected array value");
			static_assert(arr[4] == 4, "unexpected array value");
			static_assert(arr[5] == 5, "unexpected array value");
			static_assert(arr[6] == 6, "unexpected array value");
			static_assert(arr[7] == 7, "unexpected array value");
			static_assert(arr[8] == 8, "unexpected array value");
			static_assert(arr[9] == 9, "unexpected array value");
		}

		{
			constexpr auto arr = to_array({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});

			static_assert(is_same_v<remove_cv_t<decltype(arr)>, eastl::array<int, 10>>, "unexpected return type");

			static_assert(arr[0] == 0, "unexpected array value");
			static_assert(arr[1] == 1, "unexpected array value");
			static_assert(arr[2] == 2, "unexpected array value");
			static_assert(arr[3] == 3, "unexpected array value");
			static_assert(arr[4] == 4, "unexpected array value");
			static_assert(arr[5] == 5, "unexpected array value");
			static_assert(arr[6] == 6, "unexpected array value");
			static_assert(arr[7] == 7, "unexpected array value");
			static_assert(arr[8] == 8, "unexpected array value");
			static_assert(arr[9] == 9, "unexpected array value");
		}

		{
			constexpr auto arr = to_array<long>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});

			static_assert(is_same_v<remove_cv_t<decltype(arr)>, eastl::array<long, 10>>, "unexpected return type");

			static_assert(arr[0] == 0l, "unexpected array value");
			static_assert(arr[1] == 1l, "unexpected array value");
			static_assert(arr[2] == 2l, "unexpected array value");
			static_assert(arr[3] == 3l, "unexpected array value");
			static_assert(arr[4] == 4l, "unexpected array value");
			static_assert(arr[5] == 5l, "unexpected array value");
			static_assert(arr[6] == 6l, "unexpected array value");
			static_assert(arr[7] == 7l, "unexpected array value");
			static_assert(arr[8] == 8l, "unexpected array value");
			static_assert(arr[9] == 9l, "unexpected array value");
		}

		{
			constexpr auto arr = to_array<unsigned long>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});

			static_assert(is_same_v<remove_cv_t<decltype(arr)>, eastl::array<unsigned long, 10>>, "unexpected return type");

			static_assert(arr[0] == 0ul, "unexpected array value");
			static_assert(arr[1] == 1ul, "unexpected array value");
			static_assert(arr[2] == 2ul, "unexpected array value");
			static_assert(arr[3] == 3ul, "unexpected array value");
			static_assert(arr[4] == 4ul, "unexpected array value");
			static_assert(arr[5] == 5ul, "unexpected array value");
			static_assert(arr[6] == 6ul, "unexpected array value");
			static_assert(arr[7] == 7ul, "unexpected array value");
			static_assert(arr[8] == 8ul, "unexpected array value");
			static_assert(arr[9] == 9ul, "unexpected array value");
		}

		{
			constexpr auto arr = to_array("EASTL");

			static_assert(is_same_v<remove_cv_t<decltype(arr)>, eastl::array<char, 6>>, "unexpected return type");

			static_assert(arr[0] == 'E', "unexpected value in array");
			static_assert(arr[1] == 'A', "unexpected value in array");
			static_assert(arr[2] == 'S', "unexpected value in array");
			static_assert(arr[3] == 'T', "unexpected value in array");
			static_assert(arr[4] == 'L', "unexpected value in array");
		}

		// Older Microsoft compilers don't implement guaranteed copy ellision which is problematic when dealing with
		// non-copyable types. We disable this test unless we are on a version of MSVC with those features.
		#if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1920) // VS2019 16.0+
		{
			struct LocalNonCopyable
			{
				LocalNonCopyable() = default;
				~LocalNonCopyable() = default;

				LocalNonCopyable(LocalNonCopyable&&) = default;
				LocalNonCopyable& operator=(LocalNonCopyable&&) = default;

				LocalNonCopyable(const LocalNonCopyable&) = delete;
				LocalNonCopyable& operator=(const LocalNonCopyable&) = delete;
			};

			constexpr auto arr = to_array({LocalNonCopyable{}});
			static_assert(arr.size() == 1, "unexpected error");
		}
		#endif
	}

	return nErrorCount;
}









